"""
ColoredFormatter类带着一些参数：
format：用于输出日志的格式化字符串（必需）
datefmt：一个传递给基类的可选的日期格式。见logging.Formatter
reset：隐性的添加一个颜色重置代码到消息输出，除非输出已经结束。默认为True
log_colors：记录级别名称到颜色名称的一个映射。可以在colorlog.default_log_colors或下面的例子中找到默认值
secondary_log_colors：颜色名称到log_colors样式映射的映射，可以在格式化字符串中使用其定义的其他颜色。 请参考下面的示例
style：在python3.2以以上可用。见logging.Formatter
可以向格式化字符串中添加参数，根据日志级别选择颜色转义码：

log_color：返回与日志级别关联的颜色
<name>_log_color：如果格式化中配置了辅助颜色，则根据日志级别返回另一种颜色（请参考下面的secondary_log_colors）
在为日志级别配置颜色时，可以使用逗号连接多个转义码（但不能直接在格式字符串中使用）。 例如，black，bg_white将在白色背景上使用转义码表示黑色文本

下面是格式化字符串中可用的：

{color}，fg_ {color}，bg_ {color}：前景色和背景色
bold，bold_{color}，fg_bold_{color}，bg_bold_{color}：粗体/明亮的颜色
reset：清除所有的格式（包括前景色和背景色）
可用的颜色名字是： black, red, green, yellow, blue, purple, cyan and white
"""

import logging
import os
import random
import sys
import multiprocessing
import time
from shutil import copyfile
from colorlog import ColoredFormatter

# log 目录
LOG_DIR = "../logs"
# 控制台标准输出等级
STD_LEVEL = logging.DEBUG
# log文件写入等级
FILE_LEVEL = logging.INFO

CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0


# noinspection PyShadowingNames
class Log:
    def __init__(self, logDir=LOG_DIR):
        # 计时器
        self.startTime = 0
        self.endTime = 0
        # 创建目录和日志文件，如果不存在。若大于1M，复制为当前日期时间的日志文件
        logPath = logDir + "/log"
        if not os.path.exists(logDir):
            os.mkdir(logDir)
            os.chmod(logDir, 0o777)
        if not os.path.exists(logPath):
            f = open(logPath, mode='w', encoding='utf-8')
            f.close()
            os.chmod(logPath, 0o777)
        if os.path.getsize(logPath) / 1048576 > 1:
            copyfile(logPath, logDir + "/log" + str(time.time()).replace(".", ""))
            with open(logPath, 'w') as f:
                f.truncate()
                f.close()
        # 定义日志文件输出格式 时间-日志等级-内容-函数
        self.loggerFormat = logging.Formatter('%(asctime)s - %(levelname)s: %(message)s')
        # 定义控制台日志输出格式颜色
        self.cLoggerFormat = ColoredFormatter(fmt='%(log_color)s%(asctime)s'
                                                  ' - %(log_color)s%(levelname)s: %(log_color)s%(message)s',
                                              reset=True,
                                              log_colors={
                                                  'DEBUG': 'cyan',
                                                  'INFO': 'green',
                                                  'WARNING': 'yellow',
                                                  'ERROR': 'bold_red',
                                                  'CRITICAL': 'bold_black,bg_yellow',
                                              },
                                              secondary_log_colors={},
                                              style='%'
                                              )
        self.logger = logging.getLogger(str(random.random()))
        self.logger.handlers.clear()
        # 设置日志级别
        self.logger.setLevel(logging.DEBUG)
        # 设置日志文件的位置，日志等级，日志格式（无色）
        self.filehandler = logging.FileHandler(logPath, mode='a')
        self.filehandler.setLevel(FILE_LEVEL)
        self.filehandler.setFormatter(self.loggerFormat)
        # 设置控制台日志 等级，日志格式（彩色）
        self.stdouthandler = logging.StreamHandler(sys.stdout)
        self.stdouthandler.setLevel(STD_LEVEL)
        self.stdouthandler.setFormatter(self.cLoggerFormat)
        # 分别添加日志控制器
        self.logger.addHandler(self.stdouthandler)
        self.logger.addHandler(self.filehandler)
        # 设置线程安全，不会死锁
        self.__lock = multiprocessing.Lock()

    # 日志输出方法
    def Log(self, msg=None, level=logging.INFO):
        if level == logging.DEBUG:
            self.logger.debug(msg)
        elif level == logging.INFO:
            self.logger.info(msg)
        elif level == logging.WARNING:
            self.logger.warning(msg)
        elif level == logging.ERROR:
            self.logger.error(msg)
        elif level == logging.CRITICAL:
            self.logger.critical(msg)

    # 保存到日志文件并打印到控制台
    def ShowAndStore(self, msg, level=10):
        self.__lock.acquire()
        self.logger.addHandler(self.stdouthandler)
        self.logger.addHandler(self.filehandler)
        self.Log(msg=msg, level=level)
        self.__lock.release()

    # 仅打印到控制台
    def Show(self, msg, level=10, funtion=""):
        self.__lock.acquire()
        self.logger.removeHandler(self.filehandler)
        self.logger.addHandler(self.stdouthandler)
        self.Log(f"{funtion} => {msg}", level=level)
        self.logger.removeHandler(self.stdouthandler)
        self.logger.addHandler(self.filehandler)
        self.__lock.release()

    # 仅保存到日志文件
    def Store(self, msg, level=20):
        self.__lock.acquire()
        self.logger.removeHandler(self.stdouthandler)
        self.logger.addHandler(self.filehandler)
        self.Log(msg, level=level)
        self.logger.removeHandler(self.filehandler)
        self.logger.addHandler(self.stdouthandler)
        self.__lock.release()

    # 日志装饰器，用于添加到重要函数上，记录函数调用参数及返回值
    def Wrapper(self, *args):
        title = ' '.join(args)

        def wrapper(func):
            def inner(*args, **kwargs):
                ret = func(*args, **kwargs)
                self.Show(f'\ntxt: {title}\n'
                          f'func: {getattr(func, "__name__")} call {str(args)} {str(kwargs)}\n'
                          f'return: {str(ret)}', 20)
                return ret

            return inner

        return wrapper

    # 计时开始
    def TimeStar(self):
        self.startTime = time.time()

    # 计时结束
    def TimeEnd(self, name=None):
        self.endTime = time.time()
        if name is not None:
            my = f"【{name}】耗时:{round(self.endTime - self.startTime, 5)}s"
        else:
            my = f"耗时:{round(self.endTime - self.startTime, 5)}s"
        self.Show(my, 30)


'''
Log = Log()

@Log.Wrapper("wrapper for test")
def test(a, b, c=5):
    return a + b + c
    
test(1, 2, 3)
'''
'''
if __name__ == "__main__":
    CRITICAL = 50
    FATAL = CRITICAL
    ERROR = 40
    WARNING = 30
    WARN = WARNING
    INFO = 20
    DEBUG = 10
    NOTSET = 0

    Log = Log()
    Log.TimeStar()
    Log.LogShow('1.DEBUG ', 10)
    Log.LogShow('1.INFO ', 20)
    Log.LogShow('1.WARN ', logging.WARN)
    Log.LogShow('1.WARNING ', 30)
    Log.LogShow('1.ERROR ', 40)
    Log.LogShow('1.FATAL ', logging.FATAL)
    Log.LogShow('1.CRITICAL ', 50)

    Log.LogStore('2.DEBUG ', logging.DEBUG)
    Log.LogStore('2.INFO ', logging.INFO)
    Log.LogStore('2.WARN ', logging.WARN)
    Log.LogStore('2.WARNING ', logging.WARNING)
    Log.LogStore('2.ERROR ', logging.ERROR)
    Log.LogStore('2.FATAL ', logging.FATAL)
    Log.LogStore('2.CRITICAL ', logging.CRITICAL)

    Log.LogShowAndStore('3.DEBUG ', logging.DEBUG)
    Log.LogShowAndStore('3.INFO ', logging.INFO)
    Log.LogShowAndStore('3.WARN ', logging.WARN)
    Log.LogShowAndStore('3.WARNING ', logging.WARNING)
    Log.LogShowAndStore('3.ERROR ', logging.ERROR)
    Log.LogShowAndStore('3.FATAL ', logging.FATAL)
    Log.LogShowAndStore('3.CRITICAL ', logging.CRITICAL)
    Log.TimeEnd()
'''
